package org.labj;

import org.libj.excel.ExcelHandler;
import org.libj.excel.anno.EnableExport;
import org.libj.excel.anno.EnableExportField;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@EnableExport(filename = "")
public class PArySeqPair {
    @EnableExportField(colName = "p")
    public int p;
    @EnableExportField(colName = "pArySeq", getter = "getPArySeq")
    public List<List<Integer>> pArySeq;  // (p, p^2) p元序列

    @EnableExportField(colName = "index1u")
    public int index1u;
    @EnableExportField(colName = "up1u")
    public int up1u;
    @EnableExportField(colName = "left1u")
    public int left1u;
    @EnableExportField(colName = "interleaveU1", getter = "getInterleaveU1")
    public List<Integer> interleaveU1;

    @EnableExportField(colName = "index2u")
    public int index2u;
    @EnableExportField(colName = "up2u")
    public int up2u;
    @EnableExportField(colName = "left2u")
    public int left2u;
    @EnableExportField(colName = "interleaveU2", getter = "getInterleaveU2")
    public List<Integer> interleaveU2;

    @EnableExportField(colName = "index3u")
    public int index3u;
    @EnableExportField(colName = "up3u")
    public int up3u;
    @EnableExportField(colName = "left3u")
    public int left3u;
    @EnableExportField(colName = "interleaveU3", getter = "getInterleaveU3")
    public List<Integer> interleaveU3;

    @EnableExportField(colName = "interleaveU", getter = "getInterleaveU")
    public List<Integer> interleaveU;


    @EnableExportField(colName = "index1v")
    public int index1v;
    @EnableExportField(colName = "up1v")
    public int up1v;
    @EnableExportField(colName = "left1v")
    public int left1v;
    @EnableExportField(colName = "interleaveV1", getter = "getInterleaveV1")
    public List<Integer> interleaveV1;

    @EnableExportField(colName = "index2v")
    public int index2v;
    @EnableExportField(colName = "up2v")
    public int up2v;
    @EnableExportField(colName = "left2v")
    public int left2v;
    @EnableExportField(colName = "interleaveV2", getter = "getInterleaveV2")
    public List<Integer> interleaveV2;

    @EnableExportField(colName = "index3v")
    public int index3v;
    @EnableExportField(colName = "up3v")
    public int up3v;
    @EnableExportField(colName = "left3v")
    public int left3v;
    @EnableExportField(colName = "interleaveV3", getter = "getInterleaveV3")
    public List<Integer> interleaveV3;

    @EnableExportField(colName = "interleaveV", getter = "getInterleaveV")
    public List<Integer> interleaveV;

    class Complex {
        public double real;
        public double imag;

        public Complex(double real, double imag) {
            DecimalFormat df = new DecimalFormat("#.##"); // format to two decimal places
            this.real = Double.parseDouble(df.format(real));
            this.imag = Double.parseDouble(df.format(imag));
            if (this.real == -0.0) {
                this.real = 0.0;
            }
            if (this.imag == -0.0) {
                this.imag = 0.0;
            }
        }

        @Override
        public String toString() {
            return real + (imag >= 0 ? "+" : "") + imag + "*i";
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Complex complex = (Complex) o;
            return Double.compare(complex.real, real) == 0 && Double.compare(complex.imag, imag) == 0;
        }

        @Override
        public int hashCode() {
            return Objects.hash(real, imag);
        }

    }

    public List<Map<Integer, Integer>> correlationList;

    @EnableExportField(colName = "correlation")
    public List<Complex> correlation;
    @EnableExportField(colName = "distinctCorr")
    public List<Complex> distinctCorr;

    @EnableExportField(colName = "values")
    public int values;

    @EnableExportField(colName = "ele1", getter = "getEle1")
    public Complex ele1;
    @EnableExportField(colName = "num1")
    public int num1;
    @EnableExportField(colName = "ele2", getter = "getEle2")
    public Complex ele2;
    @EnableExportField(colName = "num2")
    public int num2;
    @EnableExportField(colName = "ele3", getter = "getEle3")
    public Complex ele3;
    @EnableExportField(colName = "num3")
    public int num3;

    public PArySeqPair() {
    }

    public PArySeqPair(int p,
                       int index1u, int up1u, int left1u,
                       int index2u, int up2u, int left2u,
                       int index3u, int up3u, int left3u,
                       int index1v, int up1v, int left1v,
                       int index2v, int up2v, int left2v,
                       int index3v, int up3v, int left3v) {
        this.p = p;

        // calculating p-ary sequence
        pArySeq = new ArrayList<>();
        for (int j = 0; j < p; j++) {
            List<Integer> pAry = new ArrayList<>();
            for (int t = 0; t < p * p; t++) {
                if (t % p != 0) {
                    pAry.add(fermatQuotient(p, t));
                } else pAry.add(j);
            }
            pArySeq.add(pAry);
        }

        this.index1u = index1u;
        this.up1u = up1u;
        this.left1u = left1u;
        this.interleaveU1 = translation(pArySeq.get(index1u), up1u, p);
        this.interleaveU1 = leftShift(interleaveU1, left1u);

        this.index2u = index2u;
        this.up2u = up2u;
        this.left2u = left2u;
        this.interleaveU2 = translation(pArySeq.get(index2u), up2u, p);
        this.interleaveU2 = leftShift(interleaveU2, left2u);

        this.index3u = index3u;
        this.up3u = up3u;
        this.left3u = left3u;
        this.interleaveU3 = translation(pArySeq.get(index3u), up3u, p);
        this.interleaveU3 = leftShift(interleaveU3, left3u);

        this.index1v = index1v;
        this.up1v = up1v;
        this.left1v = left1v;
        this.interleaveV1 = translation(pArySeq.get(index1v), up1v, p);
        this.interleaveV1 = leftShift(interleaveV1, left1v);

        this.index2v = index2v;
        this.up2v = up2v;
        this.left2v = left2v;
        this.interleaveV2 = translation(pArySeq.get(index2v), up2v, p);
        this.interleaveV2 = leftShift(interleaveV2, left2v);

        this.index3v = index3v;
        this.up3v = up3v;
        this.left3v = left3v;
        this.interleaveV3 = translation(pArySeq.get(index3v), up3v, p);
        this.interleaveV3 = leftShift(interleaveV3, left3v);

        this.interleaveU = intertwine(interleaveU1, interleaveU2, interleaveU3);
        this.interleaveV = intertwine(interleaveV1, interleaveV2, interleaveV3);

        // calculating correlation
        int len = this.interleaveU.size();
        correlationList = new ArrayList<>();
        for (int step = 0; step < len; step++) {  // tao
            Map<Integer, Integer> ks = new HashMap<>();  // 统计k的频次
            for (int t = 0; t < len; t++) {
                // TODO k %= p
                int k = interleaveU.get(t) - interleaveV.get((t + step) % len);
                k %= p;
                ks.put(k, ks.getOrDefault(k, 0) + 1);
            }
            correlationList.add(ks);
        }

        correlation = new ArrayList<>();
        for (Map<Integer, Integer> map : correlationList) {
            correlation.add(k2c(map));
        }

        // distinct
        distinctCorr = correlation.stream().distinct().collect(Collectors.toList());

        values = distinctCorr.size();
        switch (values) {
            case 2:
                ele1 = distinctCorr.get(0);
                ele2 = distinctCorr.get(1);
                ele3 = new Complex(-1.01, -1.02);

                num1 = (int) correlation.stream().filter(complex -> complex.equals(ele1)).count();
                num2 = (int) correlation.stream().filter(complex -> complex.equals(ele2)).count();
                num3 = -1;
                break;
            case 3:
                ele1 = distinctCorr.get(0);
                ele2 = distinctCorr.get(1);
                ele3 = distinctCorr.get(2);

                num1 = (int) correlation.stream().filter(complex -> complex.equals(ele1)).count();
                num2 = (int) correlation.stream().filter(complex -> complex.equals(ele2)).count();
                num3 = (int) correlation.stream().filter(complex -> complex.equals(ele3)).count();
                break;
            default:
                // do nothing
                break;
        }
    }

    public String getPArySeq() {
        return pArySeq.toString();
    }

    public String getInterleaveU1() {
        return interleaveU1.toString();
    }

    public String getInterleaveU2() {
        return interleaveU2.toString();
    }

    public String getInterleaveU3() {
        return interleaveU3.toString();
    }

    public String getInterleaveU() {
        return interleaveU.toString();
    }

    public String getInterleaveV1() {
        return interleaveV1.toString();
    }

    public String getInterleaveV2() {
        return interleaveV2.toString();
    }

    public String getInterleaveV3() {
        return interleaveV3.toString();
    }

    public String getInterleaveV() {
        return interleaveV.toString();
    }


    public Complex k2c(Map<Integer, Integer> ks) {
        double real = 0.0;
        double imag = 0.0;
        for (Integer key : ks.keySet()) {
            Integer value = ks.get(key);
            real += value * (Math.cos((2 * Math.PI * key) / p));
            imag += value * (Math.sin((2 * Math.PI * key) / p));
        }
        return new Complex(real, imag);
    }

    public String getEle1() {
        return ele1.toString();
    }

    public String getEle2() {
        return ele2.toString();
    }

    public String getEle3() {
        return ele3.toString();
    }


    public static List<Map<Integer, Integer>> getCorr(int p, List<Integer> u, List<Integer> v) {
        int len = u.size();
        List<Map<Integer, Integer>> correlation = new ArrayList<>();
        for (int step = 0; step < len; step++) {  // tao
            Map<Integer, Integer> ks = new HashMap<>();  // 统计k的频次
            for (int t = 0; t < len; t++) {
                // TODO k %= p
                int k = u.get(t) - v.get((t + step) % len);
                k %= p;
                ks.put(k, ks.getOrDefault(k, 0) + 1);
            }
            correlation.add(ks);
        }
        return correlation;
    }

    /**
     * return the Fermat-quotient F(t) module p
     * F(t) := (t^(p-1)-1) / p  (mod p)
     *
     * @param p odd prime p
     * @param t t mod p != 0
     * @return F(t) (int) (((Math.pow(t, p - 1) - 1) / p) % p)
     */
    public static int fermatQuotient(int p, int t) {
        BigInteger tBig = new BigInteger(String.valueOf(t));
        return tBig.pow(p - 1).subtract(new BigInteger("1"))
                .divide(new BigInteger(String.valueOf(p)))
                .mod(new BigInteger(String.valueOf(p))).intValue();
    }

    /**
     * return left shifted sequence
     */
    public static List<Integer> leftShift(List<Integer> original, int offset) {
        List<Integer> rs = new ArrayList<>();
        int size = original.size();
        for (int t = 0; t < size; t++) {
            rs.add(original.get((t + offset) % size));
        }
        return rs;
    }

    /**
     * return translated sequence
     */
    public static List<Integer> translation(List<Integer> original, int trans, int p) {
        List<Integer> rs = new ArrayList<>();
        for (Integer integer : original) {
            rs.add((integer + trans) % p);
        }
        return rs;
    }

    /**
     * return intertwined sequence
     */
    public List<Integer> intertwine(List<Integer> set1, List<Integer> set2, List<Integer> set3) {
        List<Integer> interleaveSeq = new ArrayList<>();
        int length = set1.size();
        for (int i = 0; i < length; i++) {
            interleaveSeq.add(set1.get(i));
            interleaveSeq.add(set2.get(i));
            interleaveSeq.add(set3.get(i));
        }
        return interleaveSeq;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("Enter odd prime p: ");
        int p = scanner.nextInt();

        System.out.print("Enter number of hosts: ");
        int host = scanner.nextInt();

        final int threadPerHost = 28;
        ExecutorService executor = Executors.newFixedThreadPool(threadPerHost * host);
        List<PArySeqPair> rs = new CopyOnWriteArrayList<>();

        for (int index1u = 0; index1u < p; index1u++) {
            for (int index2u = 0; index2u < p; index2u++) {
                for (int index3u = 0; index3u < p; index3u++) {

                    for (int index1v = 0; index1v < p; index1v++) {
                        for (int index2v = 0; index2v < p; index2v++) {
                            for (int index3v = 0; index3v < p; index3v++) {

                                for (int up1u = 0; up1u < p; up1u++) {
                                    for (int up2u = 0; up2u < p; up2u++) {
                                        for (int up3u = 0; up3u < p; up3u++) {

                                            for (int up1v = 0; up1v < p; up1v++) {
                                                for (int up2v = 0; up2v < p; up2v++) {
                                                    for (int up3v = 0; up3v < p; up3v++) {

                                                        for (int left1u = 0; left1u < p * p; left1u++) {
                                                            for (int left2u = 0; left2u < p * p; left2u++) {
                                                                for (int left3u = 0; left3u < p * p; left3u++) {

                                                                    for (int left1v = 0; left1v < p * p; left1v++) {
                                                                        for (int left2v = 0; left2v < p * p; left2v++) {
                                                                            for (int left3v = 0; left3v < p * p; left3v++) {

                                                                                // submit tasks to executor
                                                                                int finalIndex1u = index1u;
                                                                                int finalUp1u = up1u;
                                                                                int finalLeft1u = left1u;
                                                                                int finalIndex2u = index2u;
                                                                                int finalUp2u = up2u;
                                                                                int finalLeft2u = left2u;
                                                                                int finalIndex3u = index3u;
                                                                                int finalUp3u = up3u;
                                                                                int finalLeft3u = left3u;
                                                                                int finalIndex1v = index1v;
                                                                                int finalUp1v = up1v;
                                                                                int finalLeft1v = left1v;
                                                                                int finalIndex2v = index2v;
                                                                                int finalUp2v = up2v;
                                                                                int finalLeft2v = left2v;
                                                                                int finalIndex3v = index3v;
                                                                                int finalUp3v = up3v;
                                                                                int finalLeft3v = left3v;
                                                                                executor.submit(() -> {
                                                                                    PArySeqPair pArySeqPair = new PArySeqPair(p,
                                                                                            finalIndex1u, finalUp1u, finalLeft1u,
                                                                                            finalIndex2u, finalUp2u, finalLeft2u,
                                                                                            finalIndex3u, finalUp3u, finalLeft3u,
                                                                                            finalIndex1v, finalUp1v, finalLeft1v,
                                                                                            finalIndex2v, finalUp2v, finalLeft2v,
                                                                                            finalIndex3v, finalUp3v, finalLeft3v);
                                                                                    if (pArySeqPair.values == 2 || pArySeqPair.values == 3) {
                                                                                        rs.add(pArySeqPair);
                                                                                    }
                                                                                });
                                                                            }
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }

                                                    }
                                                }
                                            }
                                        }
                                    }

                                }

                            }

                        }
                    }
                }
            }
        }

        executor.shutdown();
        try {
            // wait for all tasks to complete or until the timeout occurs
            boolean terminated = executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
            if (terminated) {
                System.out.println("All tasks have completed successfully");
            } else {
                System.out.println("Timeout occurred before all tasks could complete");
            }
        } catch (InterruptedException e) {
            System.out.println("Execution was interrupted before all tasks could complete");
        }
        String filename = "p=" + p + ".xls";
        Path path = Paths.get(filename);
        System.out.println("saving workbook to file:" + path.toAbsolutePath());
        try {
            ExcelHandler.exportExcel(Files.newOutputStream(path), rs, PArySeqPair.class, null);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
